package com.opencvjava.zero;

import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.springframework.stereotype.Service;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import static com.opencvjava.support.util.CvUtils.imshow;
import static com.opencvjava.support.util.CvUtils.mat;
import static com.opencvjava.zero.Unit.COLOR_FILL_THRESHOLD;
import static com.opencvjava.zero.Unit.FILL_THRESHOLD;
import static org.opencv.core.Core.BORDER_DEFAULT;
import static org.opencv.imgcodecs.Imgcodecs.IMREAD_GRAYSCALE;
import static org.opencv.imgproc.Imgproc.GaussianBlur;

@Service
public class ZeroTest {

    Mat gray;
    private int rows, cols, total, rowIteNum, colIteNum, rowIndex,
            rowStart, rowEnd, colStart, colEnd, color;
    byte[] data;
    private ImageInfo imageInfo = new ImageInfo();
    private Random random = new Random();

    public void test() {
        init(null, "lena.jpg");
        units();
        unitNeighbours();
        unitType();
        resultFill();
        blocks();
        System.out.println("block size : " + imageInfo.blockSize());
        resultBlocks();
    }

    private void resultBlocks() {
        Mat result = new Mat(gray.size(), CvType.CV_8UC3);
        byte[] dataResult = new byte[total * 3];
        for (Block block : imageInfo.getBlocks()) {
            int b = random.nextInt(256);
            int g = random.nextInt(256);
            int r = random.nextInt(256);
            for (Unit unit : block.getUnits()) {
                for (int row = unit.getMidRow() - 1; row <= unit.getMidRow() + 1; row++) {
                    rowIndex = row * cols * 3;
                    for (int col = unit.getMidCol() - 1; col <= unit.getMidCol() + 1; col++) {
                        int index = rowIndex + col * 3;
                        dataResult[index] = (byte) (b & 0xff);
                        dataResult[index + 1] = (byte) (g & 0xff);
                        dataResult[index + 2] = (byte) (r & 0xff);
                    }
                }
            }
        }
        result.put(0, 0, dataResult);
        imshow("blocks", result);
    }

    private void resultFill() {
        Mat result = new Mat(gray.size(), CvType.CV_8UC1);
        byte[] dataResult = new byte[total];
        int fillCount = 0;
        int noiseCount = 0;
        int initSize = 0;
        int colorFillCount = 0;
        int edgeCount = 0;
        for (Unit unit : imageInfo.getUnits()) {
            if (unit.isFill()) {
                fillCount++;
                for (int row = unit.getMidRow() - 1; row <= unit.getMidRow() + 1; row++) {
                    for (int col = unit.getMidCol() - 1; col <= unit.getMidCol() + 1; col++) {
                        dataResult[row * cols + col] = -1;
                    }
                }
            }
            if (unit.isNoise()) noiseCount++;
            if (unit.getUnitType().equals(Unit.UnitType.INIT)) initSize++;
            if (unit.isColorFill()) colorFillCount++;
            if (unit.isEdge()) edgeCount++;
        }
        System.out.println("------------------------");
        System.out.println("total size:" + total / 9);
        System.out.println("color fill size:" + colorFillCount);
        System.out.println("fill size:" + fillCount);
        System.out.println("noise size:" + noiseCount);
        System.out.println("edge size:" + edgeCount);
        System.out.println("init size:" + initSize);
        System.out.println("------------------------");
        result.put(0, 0, dataResult);
        imshow("fill", result);
    }

    private void blocks() {
        for (Unit unit : imageInfo.getUnits()) {
            if (!unit.isVisited() && unit.isEdge()) {
                Block block = new Block();
                LinkedList<Unit> bfs = new LinkedList<>();
                bfs.push(unit);
                unit.setVisitType(Unit.VisitType.VISITED);
                while (!bfs.isEmpty()) {
                    Unit pop = bfs.pop();
                    block.addUnit(pop);
                    Iterator<Map.Entry<Integer, Unit>> iterator = pop.getNeighbours().entrySet().iterator();
                    while (iterator.hasNext()) {
                        Map.Entry<Integer, Unit> next = iterator.next();
                        Unit u = next.getValue();
                        if (!u.isVisited() && u.isEdge()) {
                            u.setVisitType(Unit.VisitType.VISITED);
                            bfs.push(u);
                        }
                        Iterator<Map.Entry<Integer, Unit>> ite = pop.getNeighbours().entrySet().iterator();
                        while (ite.hasNext()) {
                            Map.Entry<Integer, Unit> next1 = ite.next();
                            Unit value = next1.getValue();
                            if (!value.isVisited() && value.isEdge()) {
                                value.setVisitType(Unit.VisitType.VISITED);
                                bfs.push(value);
                            }
                        }
                    }
                }
                if (block.unitSize() > 3) {
                    imageInfo.addBlock(block);
                }
            }
        }
    }

    private void unitType() {
        for (Unit unit : imageInfo.getUnits()) {
            if (unit.colorSize() > 5) {
                unit.setUnitType(Unit.UnitType.NOISE);
                continue;
            }
            int mean = unit.getMean();
            Iterator<Map.Entry<Integer, Unit>> iterator = unit.getNeighbours().entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Integer, Unit> next = iterator.next();
                Unit neighbour = next.getValue();
                if (!neighbour.isColorFill() || (mean - neighbour.getMean() > FILL_THRESHOLD)) {
                    unit.addExit(next.getKey());
                }
            }
        }
        for (Unit unit : imageInfo.getUnits()) {
            int exitSize = unit.exitSize();
            if (exitSize == 0) {
                unit.setUnitType(Unit.UnitType.FILL);
            } else if (unit.exitSize() > 5) {
                unit.setUnitType(Unit.UnitType.NOISE);
            } else {
                unit.setUnitType(Unit.UnitType.EDGE);
            }
        }
    }

    private void unitNeighbours() {
        List<Unit> unitList = imageInfo.getUnits();
        for (int index = 0; index < imageInfo.unitSize(); index++) {
            Unit unit = unitList.get(index);
            int row = index / colIteNum;
            int col = index % colIteNum;
            boolean hasTop = row - 1 > -1;
            boolean hasBottom = row + 1 < rowIteNum;
            boolean hasLeft = col - 1 > -1;
            boolean hasRight = col + 1 < colIteNum;
            if (hasTop) {
                int topIndex = (row - 1) * colIteNum + col;
                unit.addNeighbours(2, unitList.get(topIndex));
                if (hasLeft) unit.addNeighbours(1, unitList.get(topIndex - 1));
                if (hasRight) unit.addNeighbours(3, unitList.get(topIndex + 1));
            }
            if (hasLeft) unit.addNeighbours(4, unitList.get(index - 1));
            if (hasRight) unit.addNeighbours(5, unitList.get(index + 1));
            if (hasBottom) {
                int bottomIndex = (row + 1) * colIteNum + col;
                unit.addNeighbours(7, unitList.get(bottomIndex));
                if (hasLeft) unit.addNeighbours(6, unitList.get(bottomIndex - 1));
                if (hasRight) unit.addNeighbours(8, unitList.get(bottomIndex + 1));
            }
        }
    }

    private void units() {
        for (int rowIte = 0; rowIte < rowIteNum; rowIte++) {
            for (int colIte = 0; colIte < colIteNum; colIte++) {
                Unit unit = new Unit();
                rowStart = rowIte * 3;
                rowEnd = rowStart + 3;
                colStart = colIte * 3;
                colEnd = colStart + 3;
                unit.setMidRow(rowStart + 1).setMidCol(colStart + 1);
                int min = 266, max = -1;
                for (int row = rowStart; row < rowEnd; row++) {
                    rowIndex = row * cols;
                    for (int col = colStart; col < colEnd; col++) {
                        color = data[rowIndex + col] & 0xff;
                        if (min > color) min = color;
                        if (max < color) max = color;
                        unit.addData(color).addColor(color);
                    }
                }
                int sum = 0;
                for (Integer color : unit.getColors()) {
                    sum += color;
                }
                unit.setMean(sum / unit.colorSize());
                if (max - min <= COLOR_FILL_THRESHOLD) {
                    unit.setUnitType(Unit.UnitType.COLOR_FILL);
                }
                imageInfo.addUnit(unit);
            }
        }
    }

    private void init(String fold, String filename) {
        Mat src;
        if (fold == null) {
            src = mat(filename, IMREAD_GRAYSCALE);
        } else {
            src = mat(fold, filename, IMREAD_GRAYSCALE);
        }
        if (src.empty()) throw new RuntimeException("读取原图失败");
        gray = new Mat();
        GaussianBlur(src, gray, new Size(3, 3), 0, 0, BORDER_DEFAULT);
        imshow("灰度图", gray);
        rows = gray.rows();
        cols = gray.cols();
        total = (int) gray.total();
        data = new byte[total];
        gray.get(0, 0, data);
        rowIteNum = rows / 3;
        colIteNum = cols / 3;
    }
}
